package com.scan.bus.service;

import android.text.TextUtils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.scan.bus.bean.BaseEntity;
import com.scan.bus.presenter.HttpManageCallback;
import com.scan.bus.util.Common;
import com.scan.bus.util.Md5Util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;

/**
 * 示例：
 *         Map map = new HashMap();
 *         map.put("goods_id","774436");
 *         HttpManage.getInstance().get("goods_detail",map,new SimpleHttpManageCallback<EmptyEntity>(){
 *             @Override
 *             public void onSuccess(BaseEntity<EmptyEntity> t) {
 *                 super.onSuccess(t);
 *             }
 *         });
 */
public class HttpManage {

    private static HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
    private Map<String,Call> mCallMap = new HashMap<>();
    private ObjectMapper objectMapper = new ObjectMapper();
    private static HttpManage manage;
    private OkHttpClient client;
    private Retrofit retrofit;

    private HttpManage() {
    }

    public static HttpManage getInstance() {
        if (manage == null) {
            synchronized (HttpManage.class) {
                if (manage == null) {
                    manage = new HttpManage();
                }
            }
        }
        return manage;
    }

    private void createClient() {
        if (client == null) {
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            client = new OkHttpClient.Builder()
                    .addInterceptor(new HttpRequestHeader(false))
                    .addInterceptor(loggingInterceptor)
                    .retryOnConnectionFailure(true)
                    .callTimeout(30, TimeUnit.SECONDS)
                    .connectTimeout(30, TimeUnit.SECONDS)
                    .writeTimeout(30, TimeUnit.SECONDS)
                    .build();
        }
    }

    /**
     * 取消所有网络请求
     */
    public void cancelAllRequest(){
        Set<Map.Entry<String, Call>> entrySet = mCallMap.entrySet();
        Iterator<Map.Entry<String, Call>> iterator = entrySet.iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Call> next = iterator.next();
            next.getValue().cancel();
            iterator.remove();
        }
        mCallMap.clear();
    }

    /**
     * 取消指定网络请求
     * @param keys
     */
    public void cancelRequest(String... keys){
        if (keys.length <= 0)return;
        List<String> keyList = Arrays.asList(keys);
        Set<Map.Entry<String, Call>> entrySet = mCallMap.entrySet();
        Iterator<Map.Entry<String, Call>> iterator = entrySet.iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Call> next = iterator.next();
            String key = next.getKey();
            if (keyList.contains(key)){
                next.getValue().cancel();
                iterator.remove();
            }
        }
    }

    public ApiService getApiService() {
        createClient();
        if (retrofit == null)
            retrofit = new Retrofit.Builder()
                    .baseUrl(InterentTools.HTTPADDR)
                    .addConverterFactory(JacksonConverterFactory.create())
                    .client(client)
                    .build();

        return retrofit.create(ApiService.class);
    }

    /**
     * 获取数据
     *
     * @param entityCall
     * @param callback
     * @param <T>
     */
    private <T> void getNetData(Call<BaseEntity<T>> entityCall, HttpManageCallback<T> callback) {
        if (entityCall == null || callback == null) {
            return;
        }
        entityCall.enqueue(new Callback<BaseEntity<T>>() {
            @Override
            public void onResponse(Call<BaseEntity<T>> call, Response<BaseEntity<T>> response) {
                if (response == null){
                    callback.onFailure();
                    callback.onFinish();
                    return;
                }
                BaseEntity<T> entity = response.body();
                if (entity == null) {
                    callback.onFailure();
                    callback.onFinish();
                } else {
                    if (entity.status == 200) {
                        callback.onSuccess(entity);
                        callback.onFinish();
                    } else {
                        callback.onErrorCode(entity.status,entity.msg,entity);
                        callback.onFinish();
                        Common.staticToast(entity.msg);
                    }
                }
            }

            @Override
            public void onFailure(Call<BaseEntity<T>> call, Throwable t) {
                t.printStackTrace();
                callback.onFailure();
                callback.onFinish();
            }
        });
    }

    /**
     * 设置请求接口参数
     * @param params
     * @return
     */
    public Map<String, Object> setParams(String... params){
        Map<String, Object> map = new HashMap<>();
        if (params != null && params.length % 2 == 0) {
            for (int i = 0; i < params.length; i += 2) {
                if (!isEmpty(params[i + 1]))
                    map.put(params[i], params[i + 1]);
            }
        }
        sortAndMD5(map);
        return map;
    }

    /**
     * 将键值对格式成 RequestBody对象并返回
     *
     * @param map
     * @return
     */
    public RequestBody getRequestBody(Map map) {
        String stringEntry = null;
        try {
            if (objectMapper == null) {
                objectMapper = new ObjectMapper();
            }
            stringEntry = objectMapper.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=UTF-8"), stringEntry);
        return requestBody;
    }

    private boolean isEmpty(CharSequence sequence) {
        return TextUtils.isEmpty(sequence);
    }

    private void sortAndMD5(Map<String, Object> map) {
        long time = System.currentTimeMillis() / 1000;
        map.put("timestamp", String.valueOf(time));
        List<String> strs = new ArrayList<>();
        Iterator<String> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            strs.add(next);
        }
        Collections.sort(strs);
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < strs.size(); i++) {
            String key = strs.get(i);
            Object value = map.get(key);
            if (value == null) {
                value = "";
            }
            sign.append(key + "=" + value);
            sign.append("&");
            if (i == strs.size() - 1) {
//                sign.append("key=" + Constant.KEY);
            }
        }
        map.put("sign", Md5Util.getMD5(sign.toString()));
    }


//    public String getStringMD5(String str) {
//        try {
//            MessageDigest digest = MessageDigest.getInstance("MD5");
//            digest.update(str.getBytes());
//            BigInteger bigInt = new BigInteger(1, digest.digest());
//            String s = bigInt.toString(16);
//            if (s.length() < 32) {
//                int legnth = 32 - s.length();
//                for (int i = 0; i < legnth; i++) {
//                    s = "0" + s;
//                }
//            }
//            return s;
//        } catch (NoSuchAlgorithmException e) {
//            e.printStackTrace();
//        }
//        return null;
//    }

    /**
     * get请求
     * @param methodName ApiService接口对应的方法名
     * @param query  请求参数
     * @param callback 接口回调
     * @param <T>  回调类型
     */
    public <T> void get(String methodName, Map query, HttpManageCallback<T> callback){
        try{
            ApiService apiService = getApiService();
            Call<BaseEntity<T>> call;
            if (query != null){
                sortAndMD5(query);
                Method method = apiService.getClass().getMethod(methodName, Map.class);
                call = (Call<BaseEntity<T>>) method.invoke(apiService, query);
            }else {
                Method method = apiService.getClass().getMethod(methodName);
                call = (Call<BaseEntity<T>>) method.invoke(apiService);
            }
            mCallMap.put(methodName,call);
            getNetData(call,callback);
        }catch (NoSuchMethodException e){
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }


    /**
     * post请求
     * @param methodName ApiService接口对应的方法名
     * @param query  请求参数
     * @param callback 接口回调
     * @param <T>  回调类型
     */
    public <T> void post(String methodName, Map query, HttpManageCallback<T> callback){
        try{
            ApiService apiService = getApiService();
            Call<BaseEntity<T>> call;
            if (query != null){
                sortAndMD5(query);
                Method method = apiService.getClass().getMethod(methodName, RequestBody.class);
                call = (Call<BaseEntity<T>>) method.invoke(apiService, getRequestBody(query));
            }else {
                Method method = apiService.getClass().getMethod(methodName);
                call = (Call<BaseEntity<T>>) method.invoke(apiService);
            }
            mCallMap.put(methodName,call);
            getNetData(call,callback);
        }catch (NoSuchMethodException e){
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
